#!/bin/sh
# OpenVPN Project Cross Compile Build
# Copyright (C) 2008-2012 Alon Bar-Lev <alon.barlev@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

#
# Cross compile for Windows under Linux:
# IMAGEROOT=`pwd`/image-win32 CHOST=i686-w64-mingw32 CBUILD=x86_64-pc-linux-gnu ./build
# IMAGEROOT=`pwd`/image-win64 CHOST=x86_64-w64-mingw32 CBUILD=x86_64-pc-linux-gnu ./build
# Cross compile for Windows under cygwin:
# IMAGEROOT=`pwd`/image-win32 CHOST=i686-w64-mingw32 CBUILD=i686-pc-cygwin ./build
# IMAGEROOT=`pwd`/image-win64 CHOST=x86_64-w64-mingw32 CBUILD=i686-pc-cygwin ./build
# Cross compile for uClibc:
# CHOST=i586-pc-linux-uclibc CBUILD=i686-pc-linux-gnu ./build
#
OSSLSIGNCODE="${OSSLSIGNCODE:-osslsigncode}"
SIGN_DIGEST=${SIGN_DIGEST:-sha256}
SIGN_TIMESTAMP_URL="${SIGN_TIMESTAMP_URL:-http://timestamp.digicert.com}"

set -e

die() {
	echo "FATAL: $1" >&2
	exit 1
}

get_full_path() {
	( cd "$1" 2> /dev/null && pwd )
}

empty_ifelse() {
	[ -z "$1" ] && echo "$2" || echo "$3"
}

contains() {
    [ "${1#*$2}" != "$1" ] && echo "$3" || echo "$4"
}

geturl() {
	if [ -n "${WGET_MODE}" ]; then
		"${WGET}" ${WGET_OPTS} --directory-prefix="${SOURCESROOT}" "${url}"
	else
		(
			cd "${SOURCESROOT}" && \
				"${CURL}" ${CURL_OPTS} "${url}"
		)
	fi
}

download1() {
	local url="$1"
	local prefix="$(basename "${url}" | sed 's/-[0-9].*//g')"

	# LZ4 download is just "v1.9.3.tar.gz", so check for exact file exist
	if [ -f "${SOURCESROOT}/${prefix}" ] ; then
		return
	fi
	if ! [ -n "$(ls "${SOURCESROOT}/${prefix}-"[0-9]* 2> /dev/null)" ]; then
		geturl ${url} || die "Cannot download ${url}"
	fi
}

download() {
	for url in \
		${LZO_URL} \
		${LZ4_URL} \
		${OPENSSL_URL} \
		${PKCS11_HELPER_URL} \
		${TAP_WINDOWS_URL} \
		${OPENVPN_URL} \
		${OPENVPN_GUI_URL};
		do
		download1 "${url}"
	done

	if [ "$(ls "${SOURCESROOT}" | wc -l | sed 's/[ \t]//g')" != 7 ]; then
		die "sources is unclean."
	fi
}

create_layout() {
	[ -e "${IMAGEROOT}" ] && rm -fr "${IMAGEROOT}"
	[ -e "${BUILDROOT}" ] && rm -fr "${BUILDROOT}"

	if ! [ -e "${SOURCESROOT}" ]; then
		mkdir -p "${SOURCESROOT}" || die "Cannot create '${SOURCESROOT}'"
	fi
	mkdir -p "${IMAGEROOT}" || die "Cannot create '${IMAGEROOT}'"
	mkdir -p "${BUILDROOT}" || die "Cannot create '${BUILDROOT}'"
	mkdir -p "${OPENVPN_ROOT}" || die "Cannot create '${OPENVPN_ROOT}'"

	# Depcache setup
	[ -n "${USE_DEP_CACHE}" ] && [ -n "${SAVE_DEP_CACHE}" ] \
		&& die "You cannot both use and save a depcache in the same run"
	# define the depcache dir, using the supplied suffix when non-empty:
	DEPCACHEDIR="${SCRIPTROOT}/depcache${DEPCACHE_SUFFIX:+-$DEPCACHE_SUFFIX}"
	if [ -n "${SAVE_DEP_CACHE}" ]; then
		mkdir -p "${DEPCACHEDIR}" || die "Cannot create '${DEPCACHEDIR}'"
	fi

	BUILDROOT="$(get_full_path "${BUILDROOT}")"
	SOURCESROOT="$(get_full_path "${SOURCESROOT}")"
	IMAGEROOT="$(get_full_path "${IMAGEROOT}")"
}

extract() {
	local f

	for f in "${SOURCESROOT}"/*; do
		local extract=""
		echo "Extract '$f'"
		case "${f}" in
			*.gz)
				extract="gunzip -c"
			;;
			*.bz2)
				extract="bunzip2 -c"
			;;
			*.lzma)
				extract="unlzma"
			;;
			*.zip)
				extract="unzip"
			;;
			*)
				extract="cat"
			;;
		esac
		if [ "${extract}" = "unzip" ]; then
			(cd "${BUILDROOT}" && unzip "${f}") || die "Extract '${f}'"
		else
			${extract} < "${f}" | ( cd "${BUILDROOT}" && tar -xf - ) || die "Extract '${f}'"
		fi
	done

	for f in "${PATCHDIR}"/*.patch "${PATCHDIR}"/*.sh; do
		product="$(echo "${f}" | sed -e 's#.*/##g' -e 's/-[0-9].*//g')"
		dir="$(echo "${BUILDROOT}/${product}"-[0-9]*)"
		if [ -d "${dir}" ]; then
			if echo "${f}" | grep '.patch$' > /dev/null; then
				echo "Patch: '$f'"
				patch -d "${dir}" -p1 < "${f}" || die "Patch '${f}'"
			else
				echo "Running: '$f'"
				( cd "${dir}"; "${f}" ) || die "Patch '${f}'"
			fi
		fi
	done
}

# Takes a short product name (eg: 'openssl') and extracts the depcache.
depcache_extract() {
	local src="${DEPCACHEDIR}/${1}.tar.gz" 
	[ -f "${src}" ] \
		|| die "Depcache for '${1}' not found at: '${src}'"
	tar -xzf "${src}" -C "${OPENVPN_ROOT}" \
		|| die "Cannot extract depcache '${1}'"
	if [ -z "${2}" ]; then
		echo "Using a depcache tarball for '${1}"
	fi
}

# Takes a short product name (eg: 'openssl') and creates a depcache tarball.
depcache_save() {
	local dst="${DEPCACHEDIR}/${1}.tar.gz"
	(cd "${INSTALL_ROOT}" && tar -czf "${dst}" .) \
		|| die "Cannot save depcache for '${1}' to: '${dst}'"
	rm -fr "${INSTALL_ROOT}" \
		|| die "Cleanup failed for: '${INSTALL_ROOT}'"
	depcache_extract "${1}" noecho
}

build_dep() {
	if [ -z "${SAVE_DEP_CACHE}" ]; then
		INSTALL_ROOT="${OPENVPN_ROOT}"
	else
		INSTALL_ROOT="${BUILDROOT}/depcache-root"
		mkdir -p "${INSTALL_ROOT}" \
			|| die "Cannot create depcache install root at: ${INSTALL_ROOT}'"
	fi

	if [ -n "${USE_DEP_CACHE}" ]; then
		depcache_extract openssl
	else
		echo "Build openssl"
		cd "${BUILDROOT}/openssl"* || die "cd openssl"

		./Configure --prefix="/${TARGET_ROOT}" --cross-compile-prefix=${CHOST:+${CHOST}-} \
			$(empty_ifelse "${DO_STATIC}" shared no-dso) \
			$(CHOST="${VIRTUAL_CHOST}" "${SCRIPTROOT}/gentoo.config-0.9.8") \
			${CFLAGS} ${LDFLAGS} \
			no-capieng \
			no-autoload-config \
			--openssldir=/etc/ssl \
			--libdir=/lib \
			${EXTRA_OPENSSL_CONFIG} \
			|| die "Configure openssl"
		[ -n "${BUILD_FOR_WINDOWS}" ] && perl util/mkdef.pl crypto ssl NT update
		[ -z "${OPENSSL_SKIP_DEPEND}" ] && ${MAKE} depend
		${MAKE}
		${MAKE} install \
			$(contains "${OPENSSL_VERSION}" "1.0.2" "INSTALL_PREFIX" "DESTDIR")="${INSTALL_ROOT}" \
			INSTALLTOP="/" MANDIR="/tmp" > build.log 2>&1 \
			|| (cat build.log && die "make openssl")
		rm -fr "${INSTALL_ROOT}/tmp"

		[ -n "${SAVE_DEP_CACHE}" ] && depcache_save openssl
	fi


	if [ -n "${USE_DEP_CACHE}" ]; then
		depcache_extract lzo
	else
		echo "Build lzo"
		cd "${BUILDROOT}/lzo"* || die "cd lzo"
		./configure ${CONFIGOPTS} ${EXTRA_LZO_CONFIG} \
			$(empty_ifelse "${DO_STATIC}" --enable-shared --disable-shared) \
			|| die "Configure lzo"
		${MAKE} ${MAKEOPTS} ${MAKE_AUTOCONF_INSTALL_TARGET} DESTDIR="${INSTALL_ROOT}" \
			|| die "make lzo"

		[ -n "${SAVE_DEP_CACHE}" ] && depcache_save lzo
	fi

	if [ -n "${USE_DEP_CACHE}" ]; then
		depcache_extract lz4
	else
		echo "Build lz4"
		cd "${BUILDROOT}/lz4"* || die "cd lz4"
		echo ./configure ${CONFIGOPTS} ${EXTRA_LZ4_CONFIG} \
			$(empty_ifelse "${DO_STATIC}" --enable-shared --disable-shared) \
			|| die "Configure lz4"
		# lz4 does not have a configure, so pass CC/LD instead
		# ... and we only want the static library
		${MAKE} ${MAKEOPTS} CC=${CHOST}-gcc LD=${CHOST}-gcc \
			WINDRES=${CHOST}-windres TARGET_OS=MINGW32 \
			BUILD_STATIC=yes BUILD_SHARED=no \
			PREFIX=/ DESTDIR="${INSTALL_ROOT}" V=1 \
			|| die "make lz4"
		${MAKE} ${MAKEOPTS} CC=${CHOST}-gcc LD=${CHOST}-gcc \
			WINDRES=${CHOST}-windres TARGET_OS=MINGW32 \
			BUILD_STATIC=yes BUILD_SHARED=no \
			PREFIX=/ DESTDIR="${INSTALL_ROOT}" V=1 install \
			|| die "make lz4"

		[ -n "${SAVE_DEP_CACHE}" ] && depcache_save lz4
	fi

	if [ -z "${DO_STATIC}" ]; then
	  if [ -n "${USE_DEP_CACHE}" ]; then
		depcache_extract pkcs11-helper
	  else
		echo "Build pkcs11-helper"
		cd "${BUILDROOT}/pkcs11-helper"* || die "cd pkcs11-helper"
		./configure ${CONFIGOPTS} ${EXTRA_PKCS11_HELPER_CONFIG} \
			--disable-crypto-engine-gnutls \
			--disable-crypto-engine-nss \
			OPENSSL_CFLAGS="${OPENSSL_CFLAGS}" \
			OPENSSL_LIBS="${OPENSSL_LIBS}" \
			|| die "Configure pkcs11-helper"
		${MAKE} ${MAKEOPTS} ${MAKE_AUTOCONF_INSTALL_TARGET} DESTDIR="${INSTALL_ROOT}" \
			|| die "make pkcs11-helper"

		[ -n "${SAVE_DEP_CACHE}" ] && depcache_save pkcs11-helper
	  fi
	fi

	echo "tap-windows"
	cp "${BUILDROOT}"/tap-windows-*/include/* "${OPENVPN_ROOT}/include" || die "tap-windows copy"

	cd "${SCRIPTROOT}"
}

build_openvpn() {
	echo "Build openvpn"
	cd "${BUILDROOT}/openvpn-${OPENVPN_VERSION}" || die "cd openvpn"
	autoreconf -ivf # this allows to specify github url as OPENVPN_URL
	CFLAGS="${CFLAGS} ${OPT_OPENVPN_CFLAGS}"
	./configure ${CONFIGOPTS} ${EXTRA_OPENVPN_CONFIG} \
		$(empty_ifelse "${DO_STATIC}" --enable-pkcs11 --disable-plugins) \
		--enable-lzo \
		--with-special-build="${SPECIAL_BUILD}" \
		|| die "Configure openvpn"
	${MAKE} ${MAKEOPTS} ${MAKE_AUTOCONF_INSTALL_TARGET} DESTDIR="${OPENVPN_ROOT}" || die "make openvpn"

	cd "${SCRIPTROOT}"
}

build_openvpn_gui() {
	echo "Build openvpn-gui"
	cd "${BUILDROOT}/openvpn-gui"* || die "cd openvpn gui"
	./configure ${CONFIGOPTS} ${EXTRA_OPENVPN_GUI_CONFIG} \
		|| die "Configure openvpn-gui"
	${MAKE} ${MAKEOPTS} ${MAKE_AUTOCONF_INSTALL_TARGET} DESTDIR="${OPENVPN_ROOT}" || die "make openvpn-gui"

	cd "${SCRIPTROOT}"
}

copy_docs() {
	echo "Copying documents"
	mkdir -p "${OPENVPN_ROOT}/share/doc/package"
	cp "${SCRIPTROOT}/README" "${SCRIPTROOT}/COPYING"* "${OPENVPN_ROOT}/share/doc/package" || die "package docs"
}

copy_sources() {
	echo "Copying sources"
	mkdir -p "${IMAGEROOT}/src/patches"
	cp "${SOURCESROOT}"/* "${IMAGEROOT}/src" || die "sources"
	if [ "$(cd "${PATCHDIR}" && echo *)" != "*" ]; then
		cp "${PATCHDIR}"/* "${IMAGEROOT}/src/patches" || die "patches"
	fi
}

clean_empty_dirs() {
	echo "Cleaning empty directories"
	find "${IMAGEROOT}" -type d | sort -r | xargs rmdir 2> /dev/null || true
}

codesign() {
	local f="$1"
	if [ -n "${DO_SIGN}" ]; then
		echo "Signing '${f}'"
		"${OSSLSIGNCODE}" \
			sign \
			-h "${SIGN_DIGEST}" \
			-pkcs12 "${SIGN_PKCS12}" \
			-pass "${SIGN_PKCS12_PASS}" \
			-t "${SIGN_TIMESTAMP_URL}" \
			-in "${f}" \
			-out "${f}.tmp" \
			|| die "Cannot sign '${f}'"
		rm "${f}"
		mv "${f}.tmp" "${f}" || die "Cannot move into '${f}'"
	fi
}

pack() {
	echo "Packing images"
	local X="${CHOST:-$(gcc -dumpmachine)}"
	(cd "${IMAGEROOT}" && find src ) | bzip2 > "${IMAGEROOT}/openvpn-${X}-${BUILD_VERSION}-srclist.bz2" || die "srclist"
	tar -cjf "${IMAGEROOT}/openvpn-${X}-${BUILD_VERSION}-src.tar.bz2" -C "${IMAGEROOT}" src || die "src"
	find "${OPENVPN_ROOT}" -name '*.dll' -or -name '*.exe' | while read f; do
		echo "Signing ${f}"
		codesign "${f}"
	done || die "cannot sign"
	tar -cjf "${IMAGEROOT}/openvpn-${X}-${BUILD_VERSION}-bin.tar.bz2" -C "${OPENVPN_ROOT}" . || die "openvpn"
}


aix_common() {
	export CC=cc
	export PATH="/usr/vac/bin:$PATH"
	VIRTUAL_CHOST="ppc-ibm-aix"
}

solaris_common() {
	export CC=cc
	export PATH="/usr/ccs/bin:/usr/sfw/bin:${PATH}"
	for d in /opt/solarisstudio12.3/bin /opt/solstudio12.2/bin /opt/sunstudio12.1/bin /opt/SUNWspro/bin; do
		[ -e "${d}" ] && export PATH="${d}:${PATH}"
	done

	case "$(uname -r)-$(uname -p)" in
		5.8-*) VIRTUAL_CHOST="sparc-sun-solaris2.8";;
		5.9-*) VIRTUAL_CHOST="sparc-sun-solaris2.9";;
		5.10-i386) VIRTUAL_CHOST="i386-pc-solaris2.10";;
		5.10-sparc) VIRTUAL_CHOST="sparc-sun-solaris2.10";;
		*) die "Unknown machine";;
	esac
	OPENSSL_SKIP_DEPEND=1 # sun compiler
}

main() {
	export CFLAGS="${TARGET_CFLAGS} ${EXTRA_TARGET_CFLAGS}"
	export LDFLAGS="${TARGET_LDFLAGS} ${EXTRA_TARGET_LDFLAGS}"
	create_layout
	download
	extract
	build_dep
	if [ -z "${DO_ONLY_DEPS}" ]; then
		build_openvpn
		[ -n "${BUILD_FOR_WINDOWS}" ] && build_openvpn_gui
		copy_docs
		copy_sources
		clean_empty_dirs
		pack
	fi
}

SCRIPTROOT="$(get_full_path "$(dirname "$0")")"

#CHOST
VIRTUAL_CHOST="${CHOST}"
#CTARGET
TARGET_CFLAGS=""
TARGET_LDFLAGS=""
CBUILD="${CBUILD:-${CHOST}}"
IMAGEROOT="${IMAGEROOT:-${SCRIPTROOT}/image}"
BUILDROOT="${BUILDROOT:-${SCRIPTROOT}/tmp}"
SOURCESROOT="${SOURCESROOT:-${SCRIPTROOT}/sources}"
PATCHDIR="${SCRIPTROOT}/patches"

. "${SCRIPTROOT}/build.vars" || die "Cannot source build.vars"

echo "${CHOST:-$(gcc -dumpmachine)}" | grep mingw > /dev/null && BUILD_FOR_WINDOWS=1
which "${WGET}" > /dev/null 2>&1 && WGET_MODE=1

CONFIGOPTS=" \
	--prefix=${TARGET_ROOT} \
	--libdir=${TARGET_ROOT}/lib \
	--host=${CHOST} \
	--target=${CTARGET} \
	--build=${CBUILD} \
	--program-prefix='' \
"

OPENVPN_ROOT="${IMAGEROOT}/openvpn"

if [ -z "${DO_NO_STRIP}" ]; then
	MAKE_AUTOCONF_INSTALL_TARGET="install-strip"
else
	MAKE_AUTOCONF_INSTALL_TARGET="install"
fi

# OpenVPN-GUI build expects the OPENSSL_CRYPTO_* variables to be set, even
# though they were removed from openvpn/configure.ac by commit 31b0bebe.
export OPENSSL_CRYPTO_CFLAGS="-I${OPENVPN_ROOT}/include"
export OPENSSL_CRYPTO_LIBS="-L${OPENVPN_ROOT}/lib -lcrypto"
export OPENSSL_CFLAGS="-I${OPENVPN_ROOT}/include"
export OPENSSL_LIBS="-L${OPENVPN_ROOT}/lib -lssl -lcrypto"
export LZO_CFLAGS="-I${OPENVPN_ROOT}/include"
export LZO_LIBS="-L${OPENVPN_ROOT}/lib -llzo2"
export LZ4_CFLAGS="-I${OPENVPN_ROOT}/include"
export LZ4_LIBS="-L${OPENVPN_ROOT}/lib -llz4"
export PKCS11_HELPER_CFLAGS="-I${OPENVPN_ROOT}/include"
export PKCS11_HELPER_LIBS="-L${OPENVPN_ROOT}/lib -lpkcs11-helper"
export TAP_CFLAGS="-I${OPENVPN_ROOT}/include"

if [ -n "${BUILD_FOR_WINDOWS}" ]; then
	CONFIGOPTS=" \
		${CONFIGOPTS} \
		--sbindir=/bin \
	"
	if [ -n "${DO_STATIC}" ]; then
		OPENSSL_LIBS=" \
			${OPENSSL_LIBS} \
			-lws2_32 \
			-lgdi32 \
		"
	fi
fi

while [ -n "$1" ]; do
	v="${1#*=}"
	case "$1" in
		--configuration=*)
			case "${v}" in
				aix-32)
					aix_common
					export TARGET_OBJECT_MODE=32
					;;
				aix-64)
					aix_common
					export TARGET_OBJECT_MODE=64
					export OPENSSL_FORCE64BIT=1
					;;
				aix-32-gcc)
					aix_common
					export CC=gcc
					export TARGET_OBJECT_MODE=32
					;;
				aix-64-gcc)
					aix_common
					export CC=gcc
					TARGET_CFLAGS="-maix64"
					TARGET_LDFLAGS="-maix64"
					export TARGET_OBJECT_MODE=64
					export OPENSSL_FORCE64BIT=1
					;;
				solaris-32)
					solaris_common
					;;
				solaris-64)
					solaris_common
					if cc -flags 2>&1 | grep m64 > /dev/null; then
						TARGET_CFLAGS="-m64"
						TARGET_LDFLAGS="-m64"
					else
						TARGET_CFLAGS="-xtarget=ultra -xarch=v9"
						TARGET_LDFLAGS="-xtarget=ultra -xarch=v9"
					fi
					export OPENSSL_FORCE64BIT=1
					;;
				linux-64-force31)
					linux_common
					TARGET_CFLAGS="-m31"
					TARGET_LDFLAGS="-m31"
					export OPENSSL_FORCE32BIT=1
					EXTRA_OPENSSL_CONFIG="${EXTRA_OPENSSL_CONFIG} -m31"
					;;
				linux-64-force32)
					linux_common
					TARGET_CFLAGS="-m32"
					TARGET_LDFLAGS="-m32"
					export OPENSSL_FORCE32BIT=1
					EXTRA_OPENSSL_CONFIG="${EXTRA_OPENSSL_CONFIG} -m32"
					;;
				linux-64-force64)
					linux_common
					TARGET_CFLAGS="-m64"
					TARGET_LDFLAGS="-m64"
					export OPENSSL_FORCE64BIT=1
					EXTRA_OPENSSL_CONFIG="${EXTRA_OPENSSL_CONFIG} -m64"
					;;
				native)
					;;
				*)
					die "Invalid configuration '${v}'"
					;;
			esac
			;;
		--special-build=*)
			SPECIAL_BUILD="${v}"
			;;
		--use-depcache=*)
			DEPCACHE_SUFFIX="${v}"
			USE_DEP_CACHE="defined"
			;;
		--save-depcache=*)
			DEPCACHE_SUFFIX="${v}"
			SAVE_DEP_CACHE="defined"
			;;
		--sign)
			DO_SIGN=1
			;;
		--sign-digest=*)
			SIGN_DIGEST="${v}"
			;;
		--sign-pkcs12=*)
			SIGN_PKCS12="${v}"
			;;
		--sign-pkcs12-pass=*)
			SIGN_PKCS12_PASS="${v}"
			;;
		--sign-timestamp=*)
			SIGN_TIMESTAMP_URL="${v}"
			;;
		--help|*)
			cat <<__EOF__
Usage: $0
	--configuration=
		native
		aix-32
		aix-64
		aix-32-gcc
		aix-64-gcc
		solaris-32
		solaris-64
		linux-32
		linux-64
		linux-64-force31
		linux-64-force32
		linux-64-force64

	--use-depcache=SUFFIX
	--save-depcache=SUFFIX
		Use or save a depcache tarball dir with the named suffix.
	--sign				do sign
	--sign-digest=digest		specify signing digest (default: sha256)
	--sign-pkcs12=pkcs12-file	signing PKCS#12 file
	--sign-pkcs12-pass=password	PKCS#12 file password
	--sign-timestamp=url            URL to be used for timestamp
__EOF__
			exit 1
			;;
	esac
	shift
done

if [ -n "${DO_SIGN}" ]; then
	[ -n "${SIGN_PKCS12}" ] || die "please specify PKCS#12 file"
	[ -f "${SIGN_PKCS12}" ] || die "cannot find PKCS#12 file '${SIGN_PKCS12}'"
fi

main

exit 0

